/**
 *    Push API 可以通过一个推送服务向web应用发送一个推送消息。一个应用服务端可以在任何时间发送推送消息，
 * 即使这时web应用或者UA还处于非激活状态。推送服务确保可靠且有效的向 UA 推送消息。这个推送消息又可以
 * 被分发到以当前应用为源的服务进程上去，且当前应用可以使用这个消息中的信息来更新本地状态或者为用户显示
 * 提示信息。
 *    这篇规范被设计用来配合网络推送协议使用，这个协议介绍了应用服务或者UA怎样和推送服务交互。
 */

/**
 *    Push API 让 web 应用可以与 UA 进行异步通信。这允许应用服务端在信息被 UA 知晓前给 UA 发出时间
 * 敏感的信息，而不用等待用户打开web应用。
 *    如此处定义，推送服务支持在任何时间推送消息。
 *    特殊情况下，即使当前应用没有在浏览器窗口中激活，推送消息仍然可以分发到web应用中：这里描述的行为，
 * 常用在这样的情形下：用户可能已经关闭了web应用，但当接收到推送信息时可以重新启动，这对用户后好处。
 *    当 UA 临时脱机也会发送一个推送消息。为了支持这一点，推送服务会在脱机时保存消息，直到 UA 再次可用。
 * 当应用想要记忆脱机的变化，并且确保可以及时地向 UA 提供适当的消息。推送服务会保存推送消息，直到 UA 可访问，
 * 并且消息可分发。
 *    不论用户是正在使用应用，还是 UA 正在通过工作线程、框架、后台窗口等于服务端通信时，Push API 
 * 都确保在web应用中向 UA 可靠交付，但这不是 Push API 的主要应用场景。web应用可能会选择 Push API
 * 来处理低频消息，从而避免维护服务端通信。
 *    Push API 最合适的使用场景是，在 UA 和 应用间有一个还未激活但确定存在通信信道。与 Fetch 标准
 * WebSocket 等直接消息发送机制相比，Push API 需要更多的资源，且通常由更多的限制条件。多数推送服务
 * 都限制了推送内容的大小和质量。
 */

/**
 * web应用服务端想web应用推送消息会经过如下步骤：
 *    ·应用服务通过 RFC8030 规范请求推送服务推送一个消息。这个请求使用推送终结点，包含在推送订阅中。
 *    ·推送服务分发这消息到指定的 UA，并识别推送消息的终结点
 *    ·UA 识别服务进程，如果必要会激活进程，并分发推送消息到服务进程。
 * 这个总的框架允许应用服务在响应事件中激活一个服务进程。关于这些事件的信息包含在推送事件中，让web应用
 * 可以适当的响应这些事件，而可能不需要启动网络请求。
 */

// 下面的例子展示了一个假设使用 Push API 的流程
const example1 = async () => {
  // 这里需要用到服务进程
  // https://example.com/serviceworker.js

  // https://example.com/webapp.js
  try{
    const serviceWorkerRegistration = await navigator.serviceWorker.register(
      'serviceworker.js'
    );
    const pushSubscription = await serviceWorkerRegistration.pushManager.subscribe();
    /**
     * 应用服务器需要的推送详情信息现在可用，可以使用 XHR 发送到后台
     */
    console.log(pushSubscription.endpoint);
    console.log(pushSubscription.getKey('p256dh')); // 返回与推送订阅相关的椭圆曲线Deffie-Hellman公钥。
    console.log(pushSubscription.getKey('auth')); // 返回应用服务端为消息加密用的授权密钥
  } catch (e) {
    console.log(e);
  }
}

/**
 * 应用服务发送消息需要的全部信息包含在上面例子中的 pushSubscription 的字段中，上面示例中属性包括：
 *    endpoint：是一个 URL，允许应用服务端请求分发推送信息到web应用。
 *    getKey()：这个方法用于返回加密和授权的键。每次调用返回一个新的 ArrayBuffer，包含对应键的值，如果指定的键不存在则为 null。
 */

/**
 * 扩展 ServiceWorkerRegistration 接口
 *    [SecureContext]
 *    partial interface ServiceWorkerRegistration {
 *        readyonly attribute PushManager pushManager;
 *    }
 * pushManager 属性暴露一个 PushManager 对象，与 ServiceWorkerRegistration 对象关联。
 */

/**
 * PushManager 接口定义了一些操作来访问推送服务
 *    [Exposed=(Window, Worker), SecureContext]
 *    interface PushManager {
 *        [SameObject] static readonly attribute FrozenArray<string> supportedContentEncodings;
 *        Promise<PushSubscription> subscribe(optional PushSubscriptionOptionsInit options)
 *        Promise<PushSubscription?> getSubscription();
 *        Promise<PushPermissionState> permissionState(optional PushSubscriptionOptionsInit options);
 *    }
 * supportedContentEncodings: 受支持的编码列表，可用于加密推送信息。其值来自于推送服务发送推送信息时的请求头的 Content-Encoding 字段值。UA 必须支持 'aes128gcm' 和其他编码类型
 * subscribe：订阅操作，返回一个 PushSubscription 对象
 * getSubscription：获取带有推送订阅小的 PushSubscription 对象
 * permissionState：获取推送权限状态对象
 */

/**
 * PushSubscriptionOptions 接口
 *    [Exposed=(Window, Worker), SecureContext]
 *    interface PushSubscriptionOptions {
 *        readonly attribute boolean userVisibleOnly;
 *        [SameObject] readonly attribute ArrayBuffer? applicationServerKey;
 *    }
 * userVisibleOnly: 获取时，是否返回初始化时给出的值
 * applicationServerKey: 获取时，获取时是否返回初始化时给出的值
 * 当 applicationServerKey 不是正在使用的并且推送服务需要一个可操作的原因时 UA 可能会拒绝订阅企图
 */

/**
 * PushSubscriptionOptionsInit 接口
 *    dictionary PushSubscriptionOptionsInit {
 *        boolean userVisibleOnly = false
 *        (BufferSource or DOMString)? applicationServerKey = null
 *    }
 * userVisibleOnly: 如果设置为 true，表明推送订阅只能推送那些用户可见的信息
 * PushSubscriptionOptionsInit: 表示与推送订阅相关的额外配置。一旦设置则不能更改。已经存在的推送订阅可以通过 unsubscribe 函数解订阅，并使用新的配置创建推送订阅
 */

/**
 * PushSubscription 接口
 *    [Exposed=(Window, Worker), SecureContext]
 *    interface PushSubscription {
 *        readyonly attribute USVString endpoint;
 *        readyonly attribute DOMTimeStamp? expirationTime;
 *        [SameObject] readyonly attribute PushSubscriptionOptions options;
 *        ArrayBuffer? getKey(PushEncryptionKeyName name)
 *        Promise<boolean> unsubscribe()
 *        PushSubscriptionJSON toJSON();
 *    }
 * PushSubscriptionJSON 接口 {
 *    USVString endpoint;
 *    DOMTimeStamp? expirationTime
 *    record<DOMString, USVString> keys;
 * }
 * endpoint: 返回与推送订阅相关的推送终结点。
 * expirationTime: 返回与推送订阅相关的订阅终止时间
 * options: 返回一个 PushSubscriptionOptions 对象，表示与推送订阅相关的配置
 * getKey(): 检索课用于加密和鉴权消息的键。
 * unsubscribe(): 解除订阅
 * toJSON(): 规格化部分字段的到一个对象中
 */

/**
 * PushEncryptionKeyName 枚举 { "p256dh" , ""auth }
 */